/*******************************************************\
|* Author: Trevor Sundberg
\*******************************************************/

// Header protection
#pragma once
#ifndef SKUGO_MEMBER_BINDING_HPP
#define SKUGO_MEMBER_BINDING_HPP

// Includes
#include "SkugoTypeBinding.hpp"
#include "SkugoFunctionHelpers.hpp"

namespace Skugo
{
  // Using directives
  using std::vector;

  // All things relevant to binding members
  class MemberBinding
  {
  public:

    // This is only used for determining the type of a member pointer
    // This function basically takes in a qualified type and strips it to the bare bones
    // It is never actually invoked, but rather used in compile time statements
    template <typename T>
    static T Strip(const T&);

    // Add a member to a type
    static AttributeContainer& Add(vector<MemberInfo>& list, const char* name, const TypeInfo* type, InvokerFn get, InvokerFn set);

    template <typename Member, typename Class, Member Class::* member>
    static void GenerateInstanceGet(void* self, RuntimePointer returns[], RuntimePointer parameters[])
    {
      // Get the 'this' object (typed)
      Class* selfTyped = static_cast<Class*>(self);

      // Get the value of the member
      Member& value = selfTyped->*member;

      // Get the member's value by returning it
      BindReturn<Member>::Write(returns[0], value);
    }

    template <typename Member, typename Class, Member Class::* member>
    static void GenerateInstanceSet(void* self, RuntimePointer returns[], RuntimePointer parameters[])
    {
      // Get the 'this' object (typed)
      Class* selfTyped = static_cast<Class*>(self);

      // Read the member's intended value
      Member& value = BindParameter<Member>::Read(parameters[0]);

      // Set the value of the member
      selfTyped->*member = value;
    }

    template <typename Member, typename Class, Member* member>
    static void GenerateStaticGet(void* self, RuntimePointer returns[], RuntimePointer parameters[])
    {
      // Get the value of the member
      Member& value = *member;

      // Get the member's value by returning it
      BindReturn<Member>::Write(returns[0], value);
    }

    template <typename Member, typename Class, Member* member>
    static void GenerateStaticSet(void* self, RuntimePointer returns[], RuntimePointer parameters[])
    {
      // Read the member's intended value
      Member& value = BindParameter<Member>::Read(parameters[0]);

      // Set the value of the member
      *member = value;
    }
  };

  // Add the member given all the details
  #define SkugoMemberPropertyHelper(memberKind, memberType, name, get, set) MemberBinding::Add(type.memberKind##Members, name, SkugoTypeId(memberType), get, set)

  // Generate a function pointer to get or set a member
  #define SkugoGenerateMember(memberKind, direction, member)                MemberBinding::Generate##memberKind##direction<SkugoTypeOfMember(&ThisType::member), ThisType, &ThisType::member>

  // Get the type of a member from a member pointer
  #define SkugoTypeOfMember(memberPtr)                                      decltype(MemberBinding::Strip(ThisPtrType(NULL)->*memberPtr))

  // Specific binding for data member variables
  #define SkugoMemberHelper(memberKind, member, name, get, set)             SkugoMemberPropertyHelper(memberKind, SkugoTypeOfMember(&ThisType::member), name, get, set)

  // Helpers for binding all the read/write combinations
  #define SkugoMemberHelperReadWrite(memberKind, member, name)              SkugoMemberHelper(memberKind, member, name, SkugoGenerateMember(memberKind, Get, member), SkugoGenerateMember(memberKind, Set, member))
  #define SkugoMemberHelperRead(memberKind, member, name)                   SkugoMemberHelper(memberKind, member, name, SkugoGenerateMember(memberKind, Get, member), NULL)
  #define SkugoMemberHelperWrite(memberKind, member, name)                  SkugoMemberHelper(memberKind, member, name, NULL,                                         SkugoGenerateMember(memberKind, Set, member))

  // Bind a member for read/write
  #define SkugoBindInstanceMemberReadWrite(member)                          SkugoBindInstanceMemberReadWriteAs(member, #member)
  #define SkugoBindStaticMemberReadWrite(member)                            SkugoBindStaticMemberReadWriteAs(  member, #member)

  // Bind a member for read/write with a different name
  #define SkugoBindInstanceMemberReadWriteAs(member, name)                  SkugoMemberHelperReadWrite(Instance, member, name)
  #define SkugoBindStaticMemberReadWriteAs(member, name)                    SkugoMemberHelperReadWrite(Static,   member, name)

  // Bind a member for read
  #define SkugoBindInstanceMemberRead(member)                               SkugoBindInstanceMemberReadAs(member, #member)
  #define SkugoBindStaticMemberRead(member)                                 SkugoBindStaticMemberReadAs(  member, #member)

  // Bind a member for read with a different name
  #define SkugoBindInstanceMemberReadAs(member, name)                       SkugoMemberHelperRead(Instance, member, name)
  #define SkugoBindStaticMemberReadAs(member, name)                         SkugoMemberHelperRead(Static,   member, name)

  // Bind a member for write
  #define SkugoBindInstanceMemberWrite(member)                              SkugoBindInstanceMemberWriteAs(member, #member)
  #define SkugoBindStaticMemberWrite(member)                                SkugoBindStaticMemberWriteAs(  member, #member)

  // Bind a member for write with a different name
  #define SkugoBindInstanceMemberWriteAs(member, name)                      SkugoMemberHelperWrite(Instance, member, name)
  #define SkugoBindStaticMemberWriteAs(member, name)                        SkugoMemberHelperWrite(Static,   member, name)


  // Aliases for the above macros (for shortening the length of code without losing readability)
  #define SkugoBindMember(member)                                           SkugoBindInstanceMemberReadWrite(member)
  #define SkugoBindStaticMember(member)                                     SkugoBindStaticMemberReadWrite(member)
}

// End header protection
#endif
